package com.util;

import com.model.BaseInfo;
import com.model.ClassInfo;
import com.model.attributes.*;
import com.model.pool.*;
import com.model.structure.ConstantPool;
import com.model.structure.OpcodeAndOperand;
import org.apache.commons.lang3.StringUtils;

import java.util.*;
import java.util.stream.Collectors;

/**
 * @author seven
 */
public class OutPrintJava {

    private ClassInfo info;
    private List<BaseConstantDecorate> pools;
    private String className;
    private String thisPack;
    private String longPackage = "java.lang";
    private String objectName = "Object";
    private String extendsStr = "extends ";
    private String classSeparator = "/";
    private String implementsStr = "implements ";
    private Map<String, String> fieldMap = new HashMap<>(8);
    private String init = "<init>";
    private String client = "<clinit>";
    private String initVoid = "()V";
    private String thisStr = "this";
    private String m1 = "m1";

    public OutPrintJava(ClassInfo info) {
        this.info = info;
        this.pools = info.getPoolList();
        print();
    }

    /**
     * 输出java代码
     */
    private void print() {
        //输入包
        printPackage();
        //输出引入包内容
        printImport();
        //输出类信息
        printClassName();
        //输出字段
        printField();
        //输出方法
        printMethod();
    }

    /**
     * 输入包
     */
    private void printPackage() {
        StringBuffer pack = new StringBuffer("package ");
        ConstantClassInfo classInfo = (ConstantClassInfo)pools.get(info.getThisClass() - 1);
        ConstantUtfInfo utf8Info = (ConstantUtfInfo)pools.get(classInfo.getIndex() - 1);
        String[] packages = utf8Info.getBytes().split(classSeparator);
        for (int i = 0; i < packages.length - 1; i++) {
            pack.append(packages[i]);
            if (i != packages.length - 2) {
                pack.append(".");
            }
        }
        pack.append(";");
        thisPack = Arrays.stream(Arrays.copyOfRange(packages, 0, packages.length - 1)).collect(Collectors.joining("."));
        className = packages[packages.length - 1];
        System.out.println(pack.toString());
    }

    /**
     * 输出引入包内容
     */
    private void printImport() {
        List<BaseConstantDecorate> utf8List =
            pools.stream().filter(e -> e instanceof ConstantClassInfo).collect(Collectors.toList());
        utf8List.forEach(e -> {
            ConstantClassInfo classInfo = (ConstantClassInfo)e;
            ConstantUtfInfo utf8Info = (ConstantUtfInfo)pools.get(classInfo.getIndex() - 1);
            String importStr = utf8Info.getBytes();
            if (importStr.contains(classSeparator)) {
                String m = importStr.replace(classSeparator, ".");
                if (!m.startsWith(longPackage) && !m.startsWith(thisPack)) {
                    System.out.println("import " + m + ";");
                }
            }
        });
    }

    /**
     * 输出类信息
     */
    private void printClassName() {
        StringBuffer classHeadBuffer = new StringBuffer("");
        String classHeadContent = AccessFlagsUtil.getClassAccessFlags(info.getAccessFlags().getAccessFlags());
        classHeadBuffer.append(classHeadContent + className + " ");
        String name = getClassName(info.getSuperClass() - 1);
        classHeadBuffer.append(StringUtils.isEmpty(name) ? "" : extendsStr + name + " ");
        int faceCount = info.getFaceCount().getValue();
        if (faceCount > 0) {
            List<String> faceNameList = new ArrayList<String>();
            info.getFaceList().forEach(face -> {
                String faceName = getClassName(face.getValue() - 1);
                if (!StringUtils.isEmpty(faceName)) {
                    faceNameList.add(faceName);
                }
            });
            classHeadBuffer.append(implementsStr + faceNameList.stream().collect(Collectors.joining(",")));
        }
        classHeadBuffer.append(" {");
        System.out.println(classHeadBuffer.toString());
    }

    /**
     * 输出属性
     */
    private void printField() {
        int fieldCount = info.getFieldsCount().getValue();
        if (fieldCount <= 0) {
            return;
        }
        info.getFieldsList().forEach(e -> {
            String access = AccessFlagsUtil.getFieldAccessFlags(e.getAccessFlag());
            ConstantUtfInfo utf8Info = (ConstantUtfInfo)pools.get(e.getNameIndex() - 1);
            String fieldName = utf8Info.getBytes();
            ConstantUtfInfo fieldType = (ConstantUtfInfo)pools.get(e.getDescriptorIndex() - 1);
            String typeName =
                fieldType.getBytes().substring(fieldType.getBytes().lastIndexOf(classSeparator) + 1).replace(";", "");
            fieldMap.put(fieldName, access + typeName + " " + fieldName);
            System.out.println(access + typeName + " " + fieldName+";");
        });
    }

    /**
     * 输出方法
     */
    private void printMethod() {
        int methodCount = info.getMethodsCount().getValue();
        if (methodCount <= 0) {
            return;
        }
        info.getMethodsList().forEach(e -> {
            getMethodContent(e);
        });
        System.out.println("}");
    }

    /**
     * 获取类名
     *
     * @param index
     * @return
     */
    private String getClassName(int index) {
        ConstantClassInfo classInfo = (ConstantClassInfo)pools.get(index);
        ConstantUtfInfo utf8Info = (ConstantUtfInfo)pools.get(classInfo.getIndex() - 1);
        String superOrInterface = utf8Info.getBytes();
        if (!superOrInterface.endsWith(objectName)) {
            return superOrInterface.substring(superOrInterface.lastIndexOf(classSeparator) + 1);
        }
        return "";
    }

    /**
     * 获取属性内容
     *
     * @param baseInfo
     * @return
     */
    private void getMethodContent(BaseInfo baseInfo) {
        String access = AccessFlagsUtil.getFieldAccessFlags(baseInfo.getAccessFlag());
        ConstantUtfInfo utf8Info = (ConstantUtfInfo)pools.get(baseInfo.getNameIndex() - 1);
        String fieldName = utf8Info.getBytes();
        ConstantUtfInfo fieldType = (ConstantUtfInfo)pools.get(baseInfo.getDescriptorIndex() - 1);
        String type = fieldType.getBytes();
        //获取异常信息
        String exception = createThrowsException(baseInfo);
        Map<String, String> methodFieldMap = new HashMap<String, String>(8);
        List<String> paramList = new ArrayList<String>();
        List<String> filedList = new ArrayList<String>();
        //获取参数 属性信息
        getParam(baseInfo, paramList, filedList, methodFieldMap);
        if (fieldName.equals(init)) {
            fieldName = className;
            if (type.equals(initVoid)) {
                System.out.println(access + fieldName + " ()" + (exception == null ? "" : exception) + "{\n}");
            } else {

                //生成每行代码
                System.out.println(access + fieldName + " (" + (paramList.isEmpty() ? "" :
                    paramList.stream().collect(Collectors.joining(","))) + ")" + (exception == null ? "" : exception)
                    + "{");
                //打印方法内容
                //printLineCode(filedList, filedValue, methodFieldMap);
                getParamValue(baseInfo,paramList,filedList, methodFieldMap);
                System.out.println("}");
            }
        } else if (fieldName.equals(client)) {
        } else {
            String typeName = ParamUtil.getMethodResult(type);
            System.out.println(access + typeName + " " + fieldName + " (" + (paramList.isEmpty() ? "" :
                paramList.stream().collect(Collectors.joining(","))) + ")" + (exception == null ? "" : exception)
                + "{");
            //打印方法内容
            getParamValue(baseInfo,paramList,filedList, methodFieldMap);
            System.out.println("}");
        }
        if (baseInfo.getAttributesCount() <= 0) {
            return;
        }
        baseInfo.getAttributesList().forEach(e -> {

        });
    }

    /**
     * 获取方法参数名称
     *
     * @param infoList
     * @param paramList
     * @param fieldList
     * @param methodFieldMap
     */
    private void getParamName(List<AttributesInfo> infoList, List<String> paramList, List<String> fieldList,
        Map<String, String> methodFieldMap) {

        CodeAttributesInfo codeAttributesInfo = getCodeInfo(infoList);
        if (codeAttributesInfo.getAttributesCount().intValue() <= 0) {
            return;
        }

        List<AttributesInfo> attributesInfos =
            codeAttributesInfo.getInfoList().stream().filter(e -> e instanceof LocalVariableTableInfo)
                .collect(Collectors.toList());
        if (attributesInfos == null || attributesInfos.isEmpty()) {
            return;
        }
        attributesInfos.forEach(e -> {
            LocalVariableTableInfo tableInfo = (LocalVariableTableInfo)e;
            tableInfo.getLocalVariableTableList().forEach(ex -> {
                ConstantUtfInfo name = (ConstantUtfInfo)pools.get(ex.getNameIndex() - 1);
                if (name.getBytes().equals(thisStr)) {
                    return;
                }
                ConstantUtfInfo utf8Info = (ConstantUtfInfo)pools.get(ex.getDescriptorIndex() - 1);
                String paramType = ParamUtil.getParam(utf8Info.getBytes()).replace(";", "");
                if (ex.getStartPc() == 0) {
                    paramList.add(paramType + " " + name.getBytes());
                } else {
                    fieldList.add(name.getBytes());
                    methodFieldMap.put(name.getBytes(), paramType + " " + name.getBytes());
                }
            });
        });
    }

    /**
     * 获取构造方法头
     *
     * @param baseInfo
     * @return
     */
    private void getParam(BaseInfo baseInfo, List<String> paramList, List<String> filedList,
        Map<String, String> fieldMap) {
        getParamName(baseInfo.getAttributesList(), paramList, filedList, fieldMap);
    }

    /**
     * @param baseInfo
     * @return
     */
    private String createThrowsException(BaseInfo baseInfo) {
        if (baseInfo.getAttributesCount() <= 0) {
            return null;
        }

        List<AttributesInfo> exceptions =
            baseInfo.getAttributesList().stream().filter(e -> e instanceof AttributeExceptionsInfo)
                .collect(Collectors.toList());
        if (exceptions == null || exceptions.isEmpty()) {
            return null;
        }
        List<String> exList = new ArrayList<String>();
        exceptions.forEach(e -> {
            AttributeExceptionsInfo ex = (AttributeExceptionsInfo)e;
            ConstantClassInfo classInfo =
                (ConstantClassInfo)info.getPoolList().get(ex.getExceptionIndexTableList().get(0) - 1);
            ConstantUtfInfo utf8Info = (ConstantUtfInfo)info.getPoolList().get(classInfo.getIndex() - 1);
            exList.add(utf8Info.getBytes().substring(utf8Info.getBytes().lastIndexOf(classSeparator) + 1));
        });
        return exList.isEmpty() ? null : " throws " + exList.stream().collect(Collectors.joining(","));
    }

    /**
     * 获取参数值
     *
     * @param baseInfo
     * @return
     */
    private Object getParamValue(BaseInfo baseInfo, List<String> paramList,List<String> filedValue, Map<String, String> methodFieldMap) {
        List<OpcodeAndOperand> values = getCodeInfo(baseInfo.getAttributesList()).getCodes();
        if (values == null) {
            return null;
        }
        List<String> value = new ArrayList<>();
        int lineCount = 0;
        String lastOpCode = "";
        for (int i = 0; i < values.size(); i++) {
            String opCode = values.get(i).getOpcode();
            String operand = values.get(i).getOperand().toString();
            if(opCode.equals("dup")){
                lastOpCode = opCode;
                continue;
            }
            if (Instructions.contains(opCode)) {
                String v = opCode.substring(opCode.indexOf("_") + 1);
                if (v.equals(m1)) {
                    value.add("-1");
                    continue;
                }
                String vs = opCode.startsWith("d") || opCode.startsWith("f") ?
                    (opCode.substring(opCode.indexOf("_") + 1) + ".0") : opCode.substring(opCode.indexOf("_")+ 1);
                value.add(vs);
            } else if (opCode.equals("bipush") || opCode.equals("sipush")) {
                value.add(operand);
            } else if (opCode.equals("ldc") || opCode.equals("ldc_w") || opCode.equals("ldc2_w")) {
                BaseConstantDecorate pool = pools.get(Integer.valueOf(operand) - 1);
                switch (pool.getConstantPoolInfoType()) {
                    case ConstantPoolKey.CONSTANT_Double_info:
                        ConstantDoubleInfo doubleInfo = (ConstantDoubleInfo)pool;
                        value.add(doubleInfo.getBytes().toString());
                        break;
                    case ConstantPoolKey.CONSTANT_Float_info:
                        ConstantFloatInfo floatInfo = (ConstantFloatInfo)pool;
                        value.add(floatInfo.getBytes().toString());
                        break;
                    case ConstantPoolKey.CONSTANT_String_info:
                        ConstantStringInfo stringInfo = (ConstantStringInfo)pool;
                        ConstantUtfInfo utf8Info = (ConstantUtfInfo)pools.get(stringInfo.getIndex() - 1);
                        value.add("\""+utf8Info.getBytes()+"\"");
                        break;
                    case ConstantPoolKey.CONSTANT_Long_info:
                        ConstantLongInfo longInfo = (ConstantLongInfo)pool;
                        value.add(longInfo.getBytes().toString());
                        break;
                    default:
                        ConstantIntegerInfo integerInfo = (ConstantIntegerInfo)pool;
                        value.add(integerInfo.getBytes().toString());
                        break;
                }
            } else if(opCode.equals("invokestatic")){
                int index = Integer.valueOf(operand);
                ConstantMethodInfo constantMethodInfo = (ConstantMethodInfo)pools.get(index-1);
                ConstantNameAndTypeInfo constantNameAndTypeInfo = (ConstantNameAndTypeInfo)pools.get(constantMethodInfo.getTypeIndex()-1);
                ConstantClassInfo constantClassInfo = (ConstantClassInfo)pools.get(constantMethodInfo.getClassIndex()-1);
                ConstantUtfInfo utfInfo = (ConstantUtfInfo)pools.get(constantNameAndTypeInfo.getClassIndex()-1);
                ConstantUtfInfo utfInfo2 = (ConstantUtfInfo)pools.get(constantClassInfo.getIndex()-1);
                if(!Instructions.contains(lastOpCode) && !lastOpCode.equals("bipush") && !lastOpCode.equals("sipush")){
                    String v = utfInfo2.getBytes().substring(utfInfo2.getBytes().lastIndexOf(classSeparator)+1)+"."+utfInfo.getBytes()+"("+value.get(value.size()-1)+")";
                    value.remove(value.get(value.size()-1));
                    value.add(v);
                }
            }else if(opCode.equals("invokespecial")){
                int index = Integer.valueOf(operand);
                ConstantMethodInfo constantMethodInfo = (ConstantMethodInfo)pools.get(index-1);
                ConstantNameAndTypeInfo constantNameAndTypeInfo = (ConstantNameAndTypeInfo)pools.get(constantMethodInfo.getTypeIndex()-1);
                ConstantUtfInfo utfInfo = (ConstantUtfInfo)pools.get(constantNameAndTypeInfo.getClassIndex()-1);
                ConstantUtfInfo utfInfo2 = (ConstantUtfInfo)pools.get(constantNameAndTypeInfo.getTypeIndex()-1);
                if(utfInfo.getBytes().equals("<init>") && filedValue.size()<=0){
                    continue;
                }
                if(utfInfo2.getBytes().equals("()V")){
                    String v = value.get(value.size()-1)+"()";
                    value.remove(value.size()-1);
                    value.add(v);
                }else{
                    if(lastOpCode.equals("dup")){
                        if(value.size()>0){
                            String newStr= value.get(0);
                            value.remove(0);
                            String v = value.stream().collect(Collectors.joining(","));
                            value.removeAll(value);
                            value.add(newStr+"("+v+")");
                        }
                    }
                }
            }else if(opCode.equals("invokevirtual")){

            }else if(Instructions.CONSTANT_STORE.contains(opCode)){
                 int index = Integer.valueOf(opCode.substring(opCode.indexOf("_")+ 1));
                System.out.println(methodFieldMap.get(filedValue.get(index-1-paramList.size()))+" = "+ value.get(0)+";");
                value.remove(0);
                lineCount++;
            }else if(Instructions.STORE.contains(opCode)){
                int index = Integer.valueOf(operand);
                System.out.println(methodFieldMap.get(filedValue.get(index-1-paramList.size()))+" = "+ value.get(0)+";");
                value.remove(0);
                lineCount++;
            }else if(opCode.equals("astore")){
                int index = Integer.valueOf(operand);
                System.out.println(methodFieldMap.get(filedValue.get(index-1-paramList.size()))+" = "+ value.get(0)+";");
                value.removeAll(value);
                lineCount++;
            }else if(opCode.equals("new")){
                ConstantClassInfo constantClassInfo = (ConstantClassInfo)pools.get(Integer.valueOf(operand)-1);
                ConstantUtfInfo utfInfo = (ConstantUtfInfo)pools.get(constantClassInfo.getIndex()-1);
                String classPath = utfInfo.getBytes();
                value.add(" new "+classPath.substring(classPath.lastIndexOf(classSeparator)+1));
            }else if(opCode.equals("pop")){
                System.out.println(value.get(0)+";");
                value.remove(0);
                lineCount++;
            }else if(Instructions.LOAD.contains(opCode)){
                if(opCode.contains("_")){
                    value.add(filedValue.get(Integer.valueOf(opCode.indexOf("_")+1)-paramList.size() - 1 ));
                }else {
                    value.add(filedValue.get(Integer.valueOf(operand)-paramList.size() - 1 ));
                }
            }else if(Instructions.RETURN.contains(opCode)){
                System.out.println("return "+value.get(0)+";");
                value.remove(0);
                lineCount++;
            }else if(opCode.equals("return") && lineCount >0){
                System.out.println("return;");
                value.remove(0);
                lineCount++;
            }
        } return null;
    }

    /**
     * 生成代码行
     *
     * @param filedList
     * @param filedValue
     */
    private void printLineCode(List<String> filedList, List<String> filedValue, Map<String, String> methodFieldMap) {
        if (!filedList.isEmpty()) {
            for (int i = 0; i < filedList.size(); i++) {
                String field = methodFieldMap.get(filedList.get(i));
                String value = filedValue.get(i);
                System.out.println(field + " = " + value + ";");
            }
        }
    }


    /**
     * 获取Code熟悉
     *
     * @param info
     */
    private void getCodeAttributes(AttributesInfo info) {
        if (info instanceof CodeAttributesInfo) {
            CodeAttributesInfo codeAttributesInfo = (CodeAttributesInfo)info;

        }
    }

    /**
     * 获取code属性
     *
     * @param infoList
     * @return
     */
    private CodeAttributesInfo getCodeInfo(List<AttributesInfo> infoList) {
        if (infoList == null || infoList.isEmpty()) {
            return null;
        }
        List<AttributesInfo> codes =
            infoList.stream().filter(e -> e instanceof CodeAttributesInfo).collect(Collectors.toList());
        if (codes == null || codes.isEmpty()) {
            return null;
        }
        return (CodeAttributesInfo)codes.get(0);
    }
}
